import sys
import itertools
import random
from typing import List, Set, Tuple

import pygame
from pygame.locals import *

from src.food import Food
from src.snake import Snake
from src.cfg import (
    SCREEN_WIDTH, SCREEN_HEIGHT, RED, BLUE, GREEN, BLACK,
    NODE_WIDTH, NODE_HEIGHT, UP, DOWN, LEFT, RIGHT, 
    LINE_COLOR, BG_COLOR, FONT_COLOR
)

START = 'start'
RUNNING = 'running'
GAMEEND = 'gameend'

KEY_MAP_1 = {
    K_UP: UP,
    K_DOWN: DOWN,
    K_LEFT: LEFT,
    K_RIGHT: RIGHT
}

KEY_MAP_2 = {
    K_w: UP,
    K_s: DOWN,
    K_a: LEFT,
    K_d: RIGHT
}

OPPOSITE = {
    LEFT: RIGHT,
    RIGHT: LEFT,
    UP: DOWN,
    DOWN: UP
}

ALL_POS = {*itertools.product(
    range(NODE_WIDTH, SCREEN_WIDTH-NODE_WIDTH, NODE_WIDTH),
    range(NODE_HEIGHT, SCREEN_HEIGHT-NODE_HEIGHT, NODE_HEIGHT)
)}

class Game:

    def __init__(self) -> None:
        self.window = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        self.clock = pygame.time.Clock()
        self.food_red = Food(RED)
        self.food_blue = Food(BLUE)
        self.food_green = Food(GREEN)
        self.show_line = False
        self.init_state()

    def init_state(self) -> None:
        self.scores = 0
        self.game_state = START
        self.tick = 8
        self.setup_snake_red()
        self.setup_snake_blue()
        self.snake_combine = Snake(GREEN)
        self.save_len_red = 0
        self.save_len_blue = 0
        self.combine = False
        self.need_combine = False
        self.food_red.set_pos(self.calc_free_pos())
        self.food_blue.set_pos(self.calc_free_pos())
        self.food_green.set_pos(self.calc_free_pos())

    @property
    def game_state(self):
        return self._game_state

    @game_state.setter
    def game_state(self, state):
        self._game_state = state

    def setup_snake_red(self) -> None:
        self.snake_red = Snake(RED)
        self.snake_red.init_snake(3, (540, 180))

    def setup_snake_blue(self) -> None:
        self.snake_blue = Snake(BLUE)
        self.snake_blue.init_snake(3, (180, 180))

    def check_snake_collide(self) -> Set:
        return {*self.snake_red.positions}.intersection({*self.snake_blue.positions})

    def calc_free_pos(self) -> List[Tuple[int]]:
        excepted = {
            *self.snake_red.positions,
            *self.snake_blue.positions,
            self.food_red.position,
            self.food_blue.position,
            self.food_green.position
        }
        return [*ALL_POS - excepted]
        
    def handle_event(self) -> None:

        for event in pygame.event.get():
            if event.type == QUIT:
                quit()
            if event.type == KEYDOWN:
                if self.game_state == START:
                    self.game_state = RUNNING
                if event.key == K_ESCAPE:
                    quit()
                elif event.key == K_c:
                    if self.game_state == GAMEEND:
                        self.init_state()
                elif event.key == K_l:
                    self.show_line = not self.show_line
                elif event.key == K_SPACE:
                    if self.combine:
                        self.combine = False
                        self.snake_blue.direction = OPPOSITE[self.snake_combine.direction]
                        self.snake_blue.init_snake(self.save_len_blue, self.snake_combine.tail_pos)
                        self.snake_red.direction = self.snake_combine.direction
                        self.snake_red.init_snake(self.save_len_red, self.snake_combine.head_pos)

                else:
                    if self.combine:
                        self.snake_combine.change_direction(KEY_MAP_1.get(event.key))
                    else:
                        self.snake_red.change_direction(KEY_MAP_1.get(event.key))
                        self.snake_blue.change_direction(KEY_MAP_2.get(event.key))
    
    def incr_tick(self):
        self.tick += 1
        
    def check_food_collide(
        self, snake: Snake, 
        food: Food, 
        score: int, 
        add_node: bool = True,
        add_tick: bool = False
    ) -> None:
        if snake.head_pos == food.position:
            self.scores += score
            food.set_pos(self.calc_free_pos())
            # print(self.tick)
            add_tick and self.incr_tick()
            add_node and snake.add_node()
            if random.choice([*range(10)]) == 3:
                if not self.need_combine:
                    self.need_combine = True
                    self.food_green.set_pos(self.calc_free_pos())                

    def draw_lines(self) -> None:
        for x in range(0, SCREEN_WIDTH, NODE_WIDTH):
            pygame.draw.line(self.window, LINE_COLOR, (x, 0), (x, SCREEN_HEIGHT))
        for y in range(0, SCREEN_HEIGHT, NODE_HEIGHT):
            pygame.draw.line(self.window, LINE_COLOR, (0, y), (SCREEN_WIDTH, y))

    def show_hint(self) -> None:
        show_text(
            self.window,
            (SCREEN_WIDTH//3-20, 150),
            '【↑、↓、←、→】 操控红蛇/绿蛇',
            FONT_COLOR)
        show_text(
            self.window,
            (SCREEN_WIDTH//3-20, 200),
            '【W 、S 、A 、D】 操控蓝蛇',
            FONT_COLOR)
        show_text(
            self.window,
            (SCREEN_WIDTH//3-20, 250),
            '【    SPACE    】 分开双蛇',
            FONT_COLOR)
        show_text(
            self.window,
            (SCREEN_WIDTH//3-20, 300),
            '【      L      】 显示/隐藏网格',
            FONT_COLOR)
        show_text(
            self.window, 
            (SCREEN_WIDTH//3+30, 400), 
            f'按任意键开始游戏...', 
            FONT_COLOR, font_bold=True)

    def run(self) -> None:
        while True:
            self.handle_event()

            self.window.fill(BG_COLOR)
            
            if self.game_state == START:
                self.show_hint()

            elif self.game_state == RUNNING:
                if self.show_line:
                    self.draw_lines()

                show_text(
                    self.window, 
                    (20, 20), 
                    f'分数：{self.scores}', 
                    FONT_COLOR)
                   
                if self.need_combine:
                    if not self.combine:
                        intersection = self.check_snake_collide()
                        if intersection:
                                self.combine = True
                                self.snake_combine.init_snake(
                                    self.snake_red.length + self.snake_blue.length,
                                    intersection.pop()
                                )
                                self.save_len_red = self.snake_red.length
                                self.snake_red.clear()
                                self.save_len_blue = self.snake_blue.length
                                self.snake_blue.clear()

                    self.food_green.draw(self.window)

                if self.combine:
                    self.snake_combine.move()
                    self.snake_combine.draw(self.window)

                    if self.snake_combine.head_pos == self.food_green.position:
                        self.scores += 50
                        self.save_len_red += 1
                        self.save_len_blue += 1
                        self.need_combine = False

                    self.check_food_collide(
                        self.snake_combine, self.food_red, 0, False, True)
                    self.check_food_collide(
                        self.snake_combine, self.food_blue, 0, False, True)
                else:
                    self.snake_red.move()
                    self.snake_red.draw(self.window)

                    self.snake_blue.move()
                    self.snake_blue.draw(self.window)

                    self.check_food_collide(
                        self.snake_red, self.food_red, 10)
                    self.check_food_collide(
                        self.snake_blue, self.food_red, -20, False)

                    self.check_food_collide(
                        self.snake_blue, self.food_blue, 10)
                    self.check_food_collide(
                        self.snake_red, self.food_blue, -20, False)

                self.food_red.draw(self.window)
                self.food_blue.draw(self.window)

                if (self.snake_red.is_dead() or 
                    self.snake_blue.is_dead() or
                    self.snake_combine.is_dead()) or \
                   (not self.need_combine and self.check_snake_collide()):
                    self.game_state = GAMEEND

            elif self.game_state == GAMEEND:
                show_text(
                    self.window, 
                    (SCREEN_WIDTH//4, SCREEN_HEIGHT//3),
                    f'游戏结束，总得分：{self.scores}',
                    FONT_COLOR,
                    font_size=60)
                show_text(
                    self.window,
                    (SCREEN_WIDTH//4, SCREEN_HEIGHT//2+100),
                    '【C】 继续游戏   【ESC】 结束游戏',
                    FONT_COLOR,
                )

            pygame.display.update()
            self.clock.tick(self.tick)

def quit() -> None:
    pygame.quit()
    sys.exit()

def show_text(
    sc: pygame.Surface, 
    pos: Tuple[int],
    text: str, 
    color: Tuple[int], 
    font_bold: bool = False, 
    font_size: int = 30,
    font_italic: bool = False) -> None:

    cur_font = pygame.font.SysFont('SimHei', font_size)      
    cur_font.set_bold(font_bold)
    cur_font.set_italic(font_italic)
    text_fmt = cur_font.render(text, 1, color)
    sc.blit(text_fmt, pos)
